package com.easylinkin.linkappapi.hcmy.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.easylinkin.linkappapi.hcmy.entity.CarManage;
import com.easylinkin.linkappapi.hcmy.mapper.CarManageMapper;
import com.easylinkin.linkappapi.hcmy.service.CarLocusService;
import com.easylinkin.linkappapi.hcmy.service.HCMYTrackPlaybackService;
import com.easylinkin.linkappapi.common.utils.DateUtil;
import com.easylinkin.linkappapi.common.utils.excel.ExcelConstant;
import com.easylinkin.linkappapi.common.utils.excel.ExcelTools;
import com.easylinkin.linkappapi.common.utils.io.OutputStreamUtil;
import com.easylinkin.linkappapi.device.entity.Device;
import com.easylinkin.linkappapi.device.mapper.DeviceMapper;
import com.easylinkin.linkappapi.elasticsearch.entity.SortField;
import com.easylinkin.linkappapi.elasticsearch.entity.TimeRange;
import com.easylinkin.linkappapi.elasticsearch.service.IEService;
import com.easylinkin.linkappapi.gaodemap.entity.ConvertLocationParams;
import com.easylinkin.linkappapi.gaodemap.service.GaoDeApiService;
import com.easylinkin.linkappapi.positioning.entity.*;
import com.easylinkin.linkappapi.hcmy.entity.QueryTrack;
import com.easylinkin.linkappapi.positioning.service.MonitorMapService;
import com.easylinkin.linkappapi.security.context.LinkappUserContextProducer;
import com.easylinkin.linkappapi.security.entity.LinkappUser;
import com.easylinkin.linkappapi.webcammanage.util.YYSHDateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.thymeleaf.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;

/**
 * class info :
 *
 * @author liuqihang
 * @date 2021/10/12 14:30
 */
@Slf4j
@Service
public class HCMYTrackPlaybackServiceImpl implements HCMYTrackPlaybackService {

    @Resource
    private GaoDeApiService gaoDeApiService;

    @Resource
    private IEService ieService;

    @Resource
    private LinkappUserContextProducer linkappUserContextProducer;

    @Resource
    private CarManageMapper carManageMapper;

    @Resource
    private DeviceMapper deviceMapper;

    @Resource
    private MonitorMapService monitorMapService;

    @Resource
    private CarLocusService carLocusService;

    public String getCurrentUser() throws Exception {
        LinkappUser current = linkappUserContextProducer.getCurrent();
        if(current == null || StringUtils.isEmpty(current.getTenantId())){
            throw new Exception("未获取到当前用户信息");
        }
        return current.getTenantId();
    }

    @Override
    public IPage<CarDeviceEs> getDetailPage(Page page, QueryTrack queryTrack) throws Exception {
        Assert.notNull(queryTrack.getCarCode(), "车牌号不能为空");
        Assert.notNull(queryTrack.getQueryStartTime(), "查询开始时间不能为空");
        Assert.notNull(queryTrack.getQueryEndTime(), "查询结束时间不能为空");
        //验证是否非法车牌号
        Device device = checkCarCode(queryTrack.getCarCode());
        if(device == null){
            throw new IllegalArgumentException("无效车牌号或车牌或未绑定车辆设备");
        }

        //验证时间范围区间不超过30天
        Long dateInteval = YYSHDateUtil.getDateInteval(queryTrack.getQueryStartTime(), queryTrack.getQueryEndTime(), DateUtil.DATE_TIME_FORMAT_MINUTE);
        if(dateInteval > 30*24){
            throw new IllegalArgumentException("查询时间不能超过30天");
        }

        QueryMonitorMap queryMonitorMap = new QueryMonitorMap();
        queryMonitorMap.setQueryStatus(null);
        queryMonitorMap.setCarCode(queryTrack.getCarCode());
        List<MonitorMapDevice> deviceList = carLocusService.getDeviceList(queryMonitorMap);
        if(ObjectUtils.isEmpty(deviceList)){
            throw new IllegalArgumentException("无效车牌号或车牌或未绑定车辆设备");
        }
        //获取设备流水数据
        Map<String, Object> likeQueryMap = new HashMap<>(16);
        likeQueryMap.put("deviceCode", device.getCode());

        TimeRange timeRange = new TimeRange();
        String dataPositioningTimeStr = "data.positioning_time";
        if (queryTrack.getQueryEndTime().compareTo("2021-10-01 00:00") < 0) {
            dataPositioningTimeStr = "data.positioning_time.keyword";
        }
        timeRange.setEndTime(queryTrack.getQueryEndTime()).setStartTime(queryTrack.getQueryStartTime()).setFieldName(dataPositioningTimeStr);

        TrackPage trackPage = new TrackPage();
        List<CarDeviceEs> carDeviceEsList = new ArrayList<CarDeviceEs>();

        SearchResponse searchResponse = queryEsData(likeQueryMap, (int) page.getCurrent(), (int) page.getSize(), timeRange);
        //游标查询返回ID
        String scrollId = null;
        if (Objects.nonNull(searchResponse) && Objects.equals(searchResponse.status(), RestStatus.OK)) {

            SearchHit[] hits = searchResponse.getHits().getHits();
            if(hits != null && hits.length > 0){
                long totalHits = searchResponse.getHits().totalHits;
                trackPage.setTotal(totalHits);
                for (SearchHit hit : hits) {
                    CarDeviceEs carDeviceEs = JSONObject.parseObject(hit.getSourceAsString(), CarDeviceEs.class);
                    if(carDeviceEs.getData() != null){
                        carDeviceEs.getData().setDirectionStr(convertSpeed2(carDeviceEs.getData().getDirection()));
                    }
                    carDeviceEsList.add(carDeviceEs);
                }
                scrollId = searchResponse.getScrollId();
            }
        }
        log.info("scrollId=" + scrollId);
        while(true){
            if(scrollId == null){
                break;
            }
            SearchResponse scrollResponse = ieService.getScrollData(scrollId);
            if (Objects.nonNull(scrollResponse) && Objects.equals(scrollResponse.status(), RestStatus.OK)) {
                SearchHit[] scrollHits = scrollResponse.getHits().getHits();
                if(scrollHits != null && scrollHits.length > 0){
                    log.info("scrollHits length=" + scrollHits.length);
                    for (SearchHit scrollHit : scrollHits) {
                        CarDeviceEs carDeviceEs = JSONObject.parseObject(scrollHit.getSourceAsString(), CarDeviceEs.class);
                        if(carDeviceEs.getData() != null){
                            carDeviceEs.getData().setDirectionStr(convertSpeed2(carDeviceEs.getData().getDirection()));
                        }
                        carDeviceEsList.add(carDeviceEs);
                    }
                    scrollId = scrollResponse.getScrollId();
                }
                else {
                    break;
                }
            }
            else {
                break;
            }
        }
        SearchResponse searchResponse2 = queryEsDataDesc(likeQueryMap, 1, 1, timeRange);
        if (Objects.nonNull(searchResponse2) && Objects.equals(searchResponse2.status(), RestStatus.OK)) {
            SearchHit[] hits = searchResponse2.getHits().getHits();
            for (SearchHit hit : hits) {
                CarDeviceEs carDeviceEs = JSONObject.parseObject(hit.getSourceAsString(), CarDeviceEs.class);
                if(carDeviceEs.getData() != null){
                    trackPage.setAllTotalMileage(carDeviceEs.getData().getTotalMileage());
                    break;
                }
            }
        }
        log.info("结果长度：" + carDeviceEsList.size());
        removeZeroSpeedElement(carDeviceEsList);

        reqConvert(carDeviceEsList);
        trackPage.setCurrent(page.getCurrent()).setSize(page.getSize());
        trackPage.setRecords(carDeviceEsList);
        MonitorMapDevice monitorMapDevice = deviceList.get(0);
        trackPage.setDeviceInfo(monitorMapDevice);
        carDeviceEsList = null;
        return trackPage;
    }

    /**
     * 移除速度为0的元素
     * @param carDeviceEsList
     */
    private void removeZeroSpeedElement(List<CarDeviceEs> carDeviceEsList){
        if(carDeviceEsList == null || carDeviceEsList.size() < 3){
            return ;
        }
        List<Integer> removeIndex = new ArrayList<>();
        for (int i = 1; i < (carDeviceEsList.size() - 1); i++){
            if(checkSpeedValue(carDeviceEsList.get(i - 1)) && checkSpeedValue(carDeviceEsList.get(i)) && checkSpeedValue(carDeviceEsList.get(i + 1))){
                removeIndex.add(i);
            }
        }
        for (int i = carDeviceEsList.size(); i >= 0; i--){
            if(removeIndex.contains(i)){
                carDeviceEsList.remove(i);
            }
        }
    }

    private boolean checkSpeedValue(CarDeviceEs carDeviceEs){
        if(carDeviceEs == null || carDeviceEs.getData() == null || carDeviceEs.getData().getSpeed() == null){
            return false;
        }
        if(carDeviceEs.getData().getSpeed().equals(0.0)){
            return true;
        }
        return false;
    }

    @Override
    public void reqConvert(List<CarDeviceEs> carDeviceEsList) throws Exception {
        List<ConvertLocationParams> parameters = new ArrayList<>();
        for (CarDeviceEs carDeviceEs:
                carDeviceEsList) {
            ConvertLocationParams convertLocationParams = new ConvertLocationParams();
            if(carDeviceEs.getData() != null && carDeviceEs.getData().getLongitude() != null && carDeviceEs.getData().getLatitude() != null){
                String longitudeAndLatitude = carDeviceEs.getData().getLongitude() + "," + carDeviceEs.getData().getLatitude();
                convertLocationParams.setLocation(longitudeAndLatitude);
                carDeviceEs.setLongitudeAndLatitude(longitudeAndLatitude);
                parameters.add(convertLocationParams);
            }
        }
        Map<String, String> resultMap = gaoDeApiService.convertLocation(parameters);
        if(resultMap == null || resultMap.isEmpty()){
            return ;
        }
        for (CarDeviceEs carDeviceEs:
                carDeviceEsList) {
            String longitudeAndLatitude = carDeviceEs.getLongitudeAndLatitude();
            if(Objects.nonNull(longitudeAndLatitude)){
                String convertStr = resultMap.get(longitudeAndLatitude);
                if(carDeviceEs.getData() != null){
                    if(convertStr.contains(",")){
                        String[] split = convertStr.split(",");
                        if(split.length >= 2){
                            carDeviceEs.getData().setLongitudeConvert(Double.valueOf(split[0]));
                            carDeviceEs.getData().setLatitudeConvert(Double.valueOf(split[1]));
                        }
                    }
                }
            }
        }
    }

    private Device checkCarCode(String carCode) throws Exception {
        String tenantId = getCurrentUser();
        QueryWrapper qw = new QueryWrapper();

        qw.eq("code", carCode);
        qw.eq("tenant_id", tenantId);
        List<CarManage> list = carManageMapper.selectList(qw);
        if(list == null || list.size() <= 0){
            return null;
        }
        CarManage carManage = list.get(0);
        if(StrUtil.isEmpty(carManage.getGpsId())){
            return null;
        }
        Device device = deviceMapper.selectById(carManage.getGpsId());
        return device;
    }

    private SearchResponse queryEsData(Map<String, Object> likeQueryMap, int index, int size, TimeRange timeRange){
        SortField sortField = new SortField().setFieldName("storageTime").setSortOrder(SortOrder.ASC);
        List<String> necessaryFieldList = new ArrayList<>();
        necessaryFieldList.add("positioning_time");
        SearchResponse searchResponse = ieService.getDeviceFlowListFromES(likeQueryMap, index, size, timeRange, necessaryFieldList,
                sortField, null);
        return searchResponse;
    }

    private SearchResponse queryEsDataDesc(Map<String, Object> likeQueryMap, int index, int size, TimeRange timeRange){
        SortField sortField = new SortField().setFieldName("storageTime").setSortOrder(SortOrder.DESC);
        List<String> necessaryFieldList = new ArrayList<>();
        necessaryFieldList.add("positioning_time");
        SearchResponse searchResponse = ieService.getDeviceFlowListFromES(likeQueryMap, index, size, timeRange, necessaryFieldList,
                sortField, null);
        return searchResponse;
    }

    private List<TrackExcel> convertResult(List<CarDeviceEs> records, boolean condition) throws Exception {
        Map<String, String> positionAddress = null;
        if(condition){
            positionAddress = monitorMapService.getPositionAddress(records);
        }

        List<TrackExcel> trackExcelList = new ArrayList<>();
        for (CarDeviceEs carDeviceEs:
                records) {
            TrackExcel trackExcel = new TrackExcel();
            trackExcel.setPositioningTime(carDeviceEs.getData().getPositioningTime())
                    .setStorageTime(carDeviceEs.getStorageTime())
                    .setLongitudeAndLatitude(carDeviceEs.getLongitudeAndLatitude() == null ? null : carDeviceEs.getLongitudeAndLatitude().replaceAll(",", "/"))
                    .setSpeed(carDeviceEs.getData().getSpeed())
                    .setDirection(convertSpeed2(carDeviceEs.getData().getDirection()));
            if(condition){
                trackExcel.setDetailAddress(getRealDetailAddress(carDeviceEs.getLongitudeAndLatitude(), positionAddress));
            }
            trackExcelList.add(trackExcel);
        }
        return trackExcelList;
    }

    @Override
    public void exportDetail(QueryTrack queryTrack, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Page page = new Page();
        page.setCurrent(1).setSize(10000);
        IPage<CarDeviceEs> detailPage = getDetailPage(page, queryTrack);
        List<CarDeviceEs> records = detailPage.getRecords();
        List<TrackExcel> trackExcelList = convertResult(records, true);
        records = null;

        //  表头
        String keyValue = "定位时间:positioningTime,接收时间:storageTime,经度/纬度:longitudeAndLatitude,速度(km/h):speed,方向:direction,位置:detailAddress";

        String title = "轨迹明细数据表";
        String fileName = title + ".xls";
        List<String> stringList = new ArrayList<String>();
        try {
            OutputStream outputStream = OutputStreamUtil
                    .getOutputStream(request, response, fileName);
            ExcelTools.exportBankExcelV2(outputStream, keyValue, trackExcelList, ExcelConstant.XLS, title, stringList);
            response.flushBuffer();
            outputStream.close();
        } catch (IOException e) {
            log.error("excel导出失败");
            throw new RuntimeException("excel导出失败！IOException异常" + e.getMessage());
        } catch (Exception e) {
            log.error("excel导出失败");
            throw new RuntimeException("excel导出失败！" + e.getMessage());
        }
    }

    private String convertSpeed2(Integer speed){
        String result = convertSpeed(speed);
        if("未知".equals(result)){
            return result;
        }else {
            result = result + "(方向数:" + speed + ")";
        }
        return result;
    }

    private String convertSpeed(Integer speed){
        if(speed == null){
            return "未知";
        }
        String speedStr = "";
        if(speed <= 20 || speed >= 340){
            speedStr = "正北向";
        }
        else if(speed > 20 && speed < 70){
            speedStr = "东北向";
        }
        else if(speed >= 70 && speed <= 110){
            speedStr = "正东向";
        }
        else if(speed > 110 && speed < 160){
            speedStr = "东南向";
        }
        else if(speed >= 160 && speed <= 200){
            speedStr = "正南向";
        }
        else if(speed > 200 && speed < 250){
            speedStr = "西南向";
        }
        else if(speed >= 250 && speed <= 290){
            speedStr = "正西向";
        }
        else if(speed > 290 && speed < 340){
            speedStr = "西北向";
        }else {
            speedStr = "未知";
        }
        return speedStr;
    }

    private String getRealDetailAddress(String longitudeAndLatitude, Map<String, String> positionAddress){
        String defaultResult = "无法获取";
        if(longitudeAndLatitude == null || "".equals(longitudeAndLatitude)){
            return defaultResult;
        }
        if(positionAddress == null || positionAddress.isEmpty()){
            return defaultResult;
        }
        String result = positionAddress.get(longitudeAndLatitude);
        if(result == null || "".equals(result) ){
            result = defaultResult;
        }
        return result;
    }
}
